Conversation
|
WalkthroughThis PR adds a comprehensive documentation suite for the AI Chat integration feature in Trigger.dev SDK. Six new MDX documentation files cover the overview and architecture, quick start guide, backend implementation patterns, frontend integration, advanced features, and API reference. Additionally, the docs navigation configuration is updated to include the new AI Chat section and add a redirect rule for existing guides. All changes are documentation-only with no modifications to the SDK codebase itself. Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/ai-chat/backend.mdx`:
- Around line 391-415: The example server actions currently trust raw chatId and
return all rows; update getChatMessages, getAllSessions, and deleteSession to
scope results to the authenticated user by using the caller's user id (from the
auth/session context) and filtering DB queries (replace db.chat.findUnique,
db.chatSession.findMany, db.chatSession.delete usages) with where: { id: chatId,
userId } or where: { userId } respectively; for deleteSession verify ownership
before deleting (return error or no-op if not owner), and for getAllSessions
only return sessions belonging to that user and avoid leaking other users'
publicAccessToken (either omit or mask tokens unless the requester owns them).
Ensure these functions take or derive the current user id and perform explicit
auth checks before DB operations.
In `@docs/ai-chat/features.mdx`:
- Around line 25-29: The example initializes userContext via chat.local with
fields name, plan, and messageCount but later reads userContext.get().userId,
causing a type mismatch; update the chat.local declaration (userContext) to
include userId in the type and initial value so the persisted object actually
contains userId, and mirror the same fix for the other occurrences referenced
(lines 39-43 and 125-134) where userId is later read so all examples are
consistent and will typecheck.
In `@docs/ai-chat/frontend.mdx`:
- Around line 71-126: The component is written as a page but expects a custom
prop chatId; update it to read route params or move logic into a client
component: either (A) change the page to export default async function Page({
params }) { const chatId = params.chatId; return <ChatClientWrapper
chatId={chatId} /> } where ChatClientWrapper is a client component that uses the
existing ChatPage/ChatClient logic, or (B) move ChatClient (and the
useEffect/load logic) into a new client component (e.g., ChatClient.tsx) and
have app/chat/[chatId]/page.tsx import that component and pass params.chatId to
it; adjust references to getChatMessages, getSession, getChatToken and
deleteSession to be called only inside client-side components (use client
directive) and ensure useTriggerChatTransport and useChat remain inside the
client component.
In `@docs/ai-chat/quick-start.mdx`:
- Line 13: The code fence language tags in docs/ai-chat/quick-start.mdx use
`ts`/`tsx` (e.g. the snippet starting with "```ts trigger/chat.ts") but our docs
convention requires full `typescript` tags; update each fenced code block
(including the ones referenced at lines 36 and 50) to use `typescript` instead
of `ts` or `tsx` so the snippets follow the project's language-tagging
guideline.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c811e887-30ff-4b0a-8fc1-304149a04299
⛔ Files ignored due to path filters (1)
references/ai-chat/ARCHITECTURE.mdis excluded by!references/**
📒 Files selected for processing (7)
docs/ai-chat/backend.mdxdocs/ai-chat/features.mdxdocs/ai-chat/frontend.mdxdocs/ai-chat/overview.mdxdocs/ai-chat/quick-start.mdxdocs/ai-chat/reference.mdxdocs/docs.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
- GitHub Check: sdk-compat / Cloudflare Workers
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: sdk-compat / Deno Runtime
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: sdk-compat / Bun Runtime
- GitHub Check: typecheck / typecheck
🧰 Additional context used
📓 Path-based instructions (4)
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.mdx: MDX documentation pages must include frontmatter with title (required), description (required), and sidebarTitle (optional) in YAML format
Use Mintlify components for structured content: , , , , , , /, /
Always import from@trigger.dev/sdkin code examples (never from@trigger.dev/sdk/v3)
Code examples must be complete and runnable where possible
Use language tags in code fences:typescript,bash,json
Files:
docs/ai-chat/quick-start.mdxdocs/ai-chat/overview.mdxdocs/ai-chat/backend.mdxdocs/ai-chat/reference.mdxdocs/ai-chat/features.mdxdocs/ai-chat/frontend.mdx
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CLAUDE.md)
Documentation in docs/ directory uses Mintlify MDX format - follow docs/CLAUDE.md for conventions
Files:
docs/ai-chat/quick-start.mdxdocs/ai-chat/overview.mdxdocs/ai-chat/backend.mdxdocs/ai-chat/reference.mdxdocs/ai-chat/features.mdxdocs/ai-chat/frontend.mdx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
docs/docs.json
docs/**/docs.json
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/docs.json: Main documentation config must be defined indocs.jsonwhich includes navigation structure, theme, and metadata
Navigation structure indocs.jsonshould be organized usingnavigation.dropdownswith groups and pages
Files:
docs/docs.json
🧠 Learnings (12)
📓 Common learnings
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3200
File: docs/config/config-file.mdx:353-368
Timestamp: 2026-03-10T12:44:19.869Z
Learning: In the triggerdotdev/trigger.dev repository, docs PRs are often written as companions to implementation PRs (e.g., PR `#3200` documents features being added in PR `#3196`). When reviewing docs PRs, the documented features may exist in a companion/companion PR branch rather than main. Always check companion PRs referenced in the PR description before flagging missing implementations.
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Keep SDK documentation in `rules/` and `.claude/skills/trigger-dev-tasks/` synchronized when features are added or changed
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Docs updates should typically be done in a separate PR from SDK feature implementation
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: New documentation pages must be added to the navigation structure in `docs.json` under the correct group after creating the MDX file
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: Applies to docs/**/*.mdx : Always import from `trigger.dev/sdk` in code examples (never from `trigger.dev/sdk/v3`)
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Keep SDK documentation in `rules/` and `.claude/skills/trigger-dev-tasks/` synchronized when features are added or changed
Applied to files:
docs/ai-chat/quick-start.mdxdocs/ai-chat/overview.mdxdocs/ai-chat/reference.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Applied to files:
docs/ai-chat/quick-start.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties
Applied to files:
docs/ai-chat/quick-start.mdxdocs/ai-chat/overview.mdxdocs/ai-chat/reference.mdx
📚 Learning: 2026-03-10T12:44:14.176Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3200
File: docs/config/config-file.mdx:353-368
Timestamp: 2026-03-10T12:44:14.176Z
Learning: In the trigger.dev repo, docs PRs are often companions to implementation PRs. When reviewing docs PRs (MDX files under docs/), check the PR description for any companion/related PR references and verify that the documented features exist in those companion PRs before flagging missing implementations. This ensures docs stay in sync with code changes across related PRs.
Applied to files:
docs/ai-chat/quick-start.mdxdocs/ai-chat/overview.mdxdocs/ai-chat/backend.mdxdocs/ai-chat/reference.mdxdocs/ai-chat/features.mdxdocs/ai-chat/frontend.mdx
📚 Learning: 2026-03-02T12:43:02.539Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: Applies to docs/**/docs.json : Navigation structure in `docs.json` should be organized using `navigation.dropdowns` with groups and pages
Applied to files:
docs/docs.json
📚 Learning: 2026-03-02T12:43:02.539Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: New documentation pages must be added to the navigation structure in `docs.json` under the correct group after creating the MDX file
Applied to files:
docs/docs.json
📚 Learning: 2026-03-02T12:43:02.539Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: Applies to docs/**/docs.json : Main documentation config must be defined in `docs.json` which includes navigation structure, theme, and metadata
Applied to files:
docs/docs.json
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Applied to files:
docs/ai-chat/reference.mdx
📚 Learning: 2026-03-06T14:44:55.489Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3173
File: packages/trigger-sdk/src/v3/chat.test.ts:103-104
Timestamp: 2026-03-06T14:44:55.489Z
Learning: In `packages/trigger-sdk/src/v3/chat.test.ts`, mocking `global.fetch` with `vi.fn()` is acceptable and intentional. `TriggerChatTransport` is a browser-facing SSE/HTTP client, and using testcontainers for these tests is not required. This file is an explicit exception to the repo's general no-mocks policy.
Applied to files:
docs/ai-chat/reference.mdxdocs/ai-chat/frontend.mdx
📚 Learning: 2026-02-25T17:28:20.456Z
Learnt from: isshaddad
Repo: triggerdotdev/trigger.dev PR: 3130
File: docs/v3-openapi.yaml:3134-3135
Timestamp: 2026-02-25T17:28:20.456Z
Learning: In the Trigger.dev codebase, the `publicAccessToken` returned by the SDK's `wait.createToken()` method is not part of the HTTP response body from `POST /api/v1/waitpoints/tokens`. The server returns only `{ id, isCached, url }`. The SDK's `prepareData` hook generates the JWT client-side from the `x-trigger-jwt-claims` response header after the HTTP call completes. The OpenAPI spec correctly documents only the HTTP response body, not SDK transformations.
<!-- [/add_learning]
Applied to files:
docs/ai-chat/reference.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `TriggerAuthContext` provider to supply Public Access Token to Trigger.dev React hooks
Applied to files:
docs/ai-chat/frontend.mdx
🪛 LanguageTool
docs/ai-chat/features.mdx
[style] ~172-~172: To form a complete sentence, be sure to include a subject.
Context: ...signal }); }, }); ``` chat.defer() can be called from anywhere during a turn —...
(MISSING_IT_THERE)
🔇 Additional comments (1)
docs/docs.json (1)
83-93: Nice nav + redirect wiring.Adding the new group under
navigation.dropdownsand redirecting the old guide entry point to the overview should make the AI Chat docs discoverable without breaking existing links.Based on learnings, "New documentation pages must be added to the navigation structure in
docs.jsonunder the correct group after creating the MDX file".Also applies to: 744-746
| export async function getChatMessages(chatId: string) { | ||
| const found = await db.chat.findUnique({ where: { id: chatId } }); | ||
| return found?.messages ?? []; | ||
| } | ||
|
|
||
| export async function getAllSessions() { | ||
| const sessions = await db.chatSession.findMany(); | ||
| const result: Record<string, { | ||
| runId: string; | ||
| publicAccessToken: string; | ||
| lastEventId?: string; | ||
| }> = {}; | ||
| for (const s of sessions) { | ||
| result[s.id] = { | ||
| runId: s.runId, | ||
| publicAccessToken: s.publicAccessToken, | ||
| lastEventId: s.lastEventId ?? undefined, | ||
| }; | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| export async function deleteSession(chatId: string) { | ||
| await db.chatSession.delete({ where: { id: chatId } }).catch(() => {}); | ||
| } |
There was a problem hiding this comment.
Scope these example server actions to the authenticated user.
getChatMessages, getAllSessions, and deleteSession trust raw chatIds or return all rows. In a multi-user app, copying this verbatim would let one client read or remove another user's chat state, and getAllSessions() would expose other users' publicAccessTokens.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/ai-chat/backend.mdx` around lines 391 - 415, The example server actions
currently trust raw chatId and return all rows; update getChatMessages,
getAllSessions, and deleteSession to scope results to the authenticated user by
using the caller's user id (from the auth/session context) and filtering DB
queries (replace db.chat.findUnique, db.chatSession.findMany,
db.chatSession.delete usages) with where: { id: chatId, userId } or where: {
userId } respectively; for deleteSession verify ownership before deleting
(return error or no-op if not owner), and for getAllSessions only return
sessions belonging to that user and avoid leaking other users' publicAccessToken
(either omit or mask tokens unless the requester owns them). Ensure these
functions take or derive the current user id and perform explicit auth checks
before DB operations.
| const userContext = chat.local<{ | ||
| name: string; | ||
| plan: "free" | "pro"; | ||
| messageCount: number; | ||
| }>({ id: "userContext" }); |
There was a problem hiding this comment.
Persist userId if the later example reads it back.
This local is initialized with name, plan, and messageCount, but the persistence example later calls userContext.get().userId. As written, the snippet won't typecheck and suggests data is available that was never stored.
🛠️ Suggested fix
const userContext = chat.local<{
+ userId: string;
name: string;
plan: "free" | "pro";
messageCount: number;
}>({ id: "userContext" });
@@
userContext.init({
+ userId: clientData.userId,
name: user.name,
plan: user.plan,
messageCount: user.messageCount,
});As per coding guidelines, "Code examples must be complete and runnable where possible".
Also applies to: 39-43, 125-134
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/ai-chat/features.mdx` around lines 25 - 29, The example initializes
userContext via chat.local with fields name, plan, and messageCount but later
reads userContext.get().userId, causing a type mismatch; update the chat.local
declaration (userContext) to include userId in the type and initial value so the
persisted object actually contains userId, and mirror the same fix for the other
occurrences referenced (lines 39-43 and 125-134) where userId is later read so
all examples are consistent and will typecheck.
| ```tsx app/page.tsx | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; | ||
| import { useChat } from "@ai-sdk/react"; | ||
| import { getChatToken, getChatMessages, getSession, deleteSession } from "@/app/actions"; | ||
|
|
||
| export default function ChatPage({ chatId }: { chatId: string }) { | ||
| const [initialMessages, setInitialMessages] = useState([]); | ||
| const [initialSession, setInitialSession] = useState(undefined); | ||
| const [loaded, setLoaded] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| async function load() { | ||
| const [messages, session] = await Promise.all([ | ||
| getChatMessages(chatId), | ||
| getSession(chatId), | ||
| ]); | ||
| setInitialMessages(messages); | ||
| setInitialSession(session ? { [chatId]: session } : undefined); | ||
| setLoaded(true); | ||
| } | ||
| load(); | ||
| }, [chatId]); | ||
|
|
||
| if (!loaded) return null; | ||
|
|
||
| return ( | ||
| <ChatClient | ||
| chatId={chatId} | ||
| initialMessages={initialMessages} | ||
| initialSessions={initialSession} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| function ChatClient({ chatId, initialMessages, initialSessions }) { | ||
| const transport = useTriggerChatTransport({ | ||
| task: "my-chat", | ||
| accessToken: getChatToken, | ||
| sessions: initialSessions, | ||
| onSessionChange: (id, session) => { | ||
| if (!session) deleteSession(id); | ||
| }, | ||
| }); | ||
|
|
||
| const { messages, sendMessage, stop, status } = useChat({ | ||
| id: chatId, | ||
| messages: initialMessages, | ||
| transport, | ||
| resume: initialMessages.length > 0, // Resume if there's an existing conversation | ||
| }); | ||
|
|
||
| // ... render UI | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Next.js App Router, what props does a default export in app/page.tsxreceive, and how should a page component access a dynamicchatId?
💡 Result:
In the App Router, the default export in app/page.tsx (a Page component) does not receive arbitrary “props” like pageProps. It only receives route-derived props:
searchParams(query string params) — and in Next.js 15, it’s passed as a Promise. [1], [2]params(dynamic route segments) — only if the route has dynamic segments; in Next.js 15, it’s also a Promise. [1], [3]
To access a dynamic chatId, make the route dynamic (e.g. app/chat/[chatId]/page.tsx) and read it from params, e.g.:
// app/chat/[chatId]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ chatId: string }>;
}) {
const { chatId } = await params;
// use chatId...
}This is the App Router pattern for dynamic segments (params) rather than expecting chatId as a normal prop. [1], [3]
Sources: [1] https://nextjs.org/docs/messages/next-prerender-sync-params, [2] https://nextjs.org/learn/dashboard-app/adding-search-and-pagination, [3] https://nextjs.org/docs/app/api-reference/file-conventions/dynamic-routes
Show a runnable app/page.tsx example that reads chatId from dynamic route params.
This snippet is labeled app/page.tsx but expects chatId as a custom prop. In App Router, page components receive only params (for dynamic route segments) and searchParams (from query strings), not arbitrary props. Move this to a non-page client component file (e.g., components/ChatClient.tsx), or rewrite it as an actual page that reads params.chatId from a route like app/chat/[chatId]/page.tsx.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/ai-chat/frontend.mdx` around lines 71 - 126, The component is written as
a page but expects a custom prop chatId; update it to read route params or move
logic into a client component: either (A) change the page to export default
async function Page({ params }) { const chatId = params.chatId; return
<ChatClientWrapper chatId={chatId} /> } where ChatClientWrapper is a client
component that uses the existing ChatPage/ChatClient logic, or (B) move
ChatClient (and the useEffect/load logic) into a new client component (e.g.,
ChatClient.tsx) and have app/chat/[chatId]/page.tsx import that component and
pass params.chatId to it; adjust references to getChatMessages, getSession,
getChatToken and deleteSession to be called only inside client-side components
(use client directive) and ensure useTriggerChatTransport and useChat remain
inside the client component.
|
|
||
| If you return a `StreamTextResult`, it's **automatically piped** to the frontend. | ||
|
|
||
| ```ts trigger/chat.ts |
There was a problem hiding this comment.
Use typescript fence tags for the snippets.
These blocks are tagged ts/tsx. The docs conventions here require typescript, and the same cleanup likely applies across the other new AI Chat pages too.
As per coding guidelines, "Use language tags in code fences: typescript, bash, json".
Also applies to: 36-36, 50-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/ai-chat/quick-start.mdx` at line 13, The code fence language tags in
docs/ai-chat/quick-start.mdx use `ts`/`tsx` (e.g. the snippet starting with
"```ts trigger/chat.ts") but our docs convention requires full `typescript`
tags; update each fenced code block (including the ones referenced at lines 36
and 50) to use `typescript` instead of `ts` or `tsx` so the snippets follow the
project's language-tagging guideline.
No description provided.